package org.tloss;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

public class MCreator {
	private SecureRandom random = new SecureRandom();
	public static final Object[] size = { new int[] { 5, 5 },
			new int[] { 5, 8 } };
	public static final int MAX_MONSTER = 2;
	public static final int MAX_STATUS = 16;
	public static final int MAX_ITEMS = 5;
	/*
	 * 1000 L 0100 R 0010 U 0001 D
	 */
	private ArrayList<Item> items = new ArrayList<Item>();
	private Status status = new Status();

	public void init() throws CloneNotSupportedException {
		int[] s = (int[]) size[random.nextInt(size.length)];
		int[][] st = new int[s[0]][s[1]];

		ItemFactory factory = new ItemFactoryImpl();
		Item item, item2;
		Location location;
		int tmp;
		boolean pickup;
		while (factory.hasNext()) {
			item = factory.buildItem();
			pickup = random.nextBoolean();
			if (pickup) {
				item.setPickup(true);
				item.setUse(false);
				tmp = random.nextInt(MAX_ITEMS - 1);
				for (int i = 0; i < tmp; i++) {
					item2 = (Item) item.clone();
					location = new Location();
					location.setX(random.nextInt(s[0]));
					location.setY(random.nextInt(s[1]));
					item2.setLocation(location);
					items.add(item2);
				}
			} else {
				item.setUse(true);
				for (int i = 0; i < MAX_ITEMS - 1; i++) {
					items.add((Item) item.clone());
				}
			}
			items.add(item);
		}
		int nMonster = random.nextInt(MAX_MONSTER);
		MonsterFactory monsterFactory = new MonsterFactoryImpl();

		Monster monster;
		for (int i = 0; i < nMonster; i++) {
			monster = monsterFactory.buildMonster();
			status.addMonster(monster);
			location = new Location();
			location.setX(random.nextInt(s[0]));
			location.setY(random.nextInt(s[1]));
			monster.setLocation(location);
		}
		location = new Location();
		location.setX(random.nextInt(s[0]));
		location.setY(random.nextInt(s[1]));
		status.setManLocation(location);
		location = new Location();
		location.setX(random.nextInt(s[0]));
		location.setY(random.nextInt(s[1]));
		if (location.getX() < location.getY()) {
			location.setX(0);
		} else {
			location.setY(0);
		}
		status.setDoor(location);
		tmp = random.nextInt(s[0] * s[1] - (nMonster + 2) * 4);
		for (int i = 0; i < tmp; i++) {
			st[i / s[1]][i % s[1]] = random.nextInt(MAX_STATUS);
		}
		status.initSize(st, s[0], s[1]);
		status.setInitItems(items);
	}

	public static int[][] MAN_MOVE = new int[][] { { 0, 0 }, { 0, 1 },
			{ 0, -1 }, { 1, 0 }, { -1, 0 } };

	public void manMove(Status status, List<Status> moves) throws Exception {
		Status ns = null;
		Location manLocation;
		List<Monster> monsters;
		for (int i = 0; i < MAN_MOVE.length; i++) {
			ns = (Status) status.clone();
			// item affect BEFFORE_MOVE
			ns.itemsEffect(Status.BEFFORE_MOVE);
			// man move
			manLocation = ns.getManLocation();
			manLocation.setX(manLocation.getX() + MAN_MOVE[i][0]);
			manLocation.setY(manLocation.getY() + MAN_MOVE[i][1]);
			if (Utils.canMove(status.getManLocation(), manLocation, ns)) {
				// item affect AFFTER_MAN_MOVE
				ns.itemsEffect(Status.AFFTER_MAN_MOVE);
				// monster move
				monsters = ns.getMonsters();
				for (int j = 0; j < monsters.size(); j++) {
					monsters.get(j).move(ns);
				}
				// item affect END_MOVE
				ns.itemsEffect(Status.END_MOVE);
				if (!ns.isLose()) {
					moves.add(ns);
				}
			}
		}
	}

	public void itemUse(Status status, List<Status> moves) throws Exception {
		Status ns = null;
		List<Item> initItems;
		List<Monster> monsters;
		Item item;
		initItems = status.getInitItems();
		for (int j = 0; initItems != null && j < initItems.size(); j++) {
			if (initItems.get(j).canUse()) {
				ns = (Status) status.clone();
				item = ns.getInitItems().remove(j);
				// item affect BEFFORE_MOVE
				ns.itemsEffect(Status.BEFFORE_MOVE);
				// user item
				item.update(ns);
				// item affect AFFTER_MAN_MOVE
				ns.itemsEffect(Status.AFFTER_MAN_MOVE);
				if (!item.useWithNoMove()) {
					// monster move
					monsters = ns.getMonsters();
					for (int k = 0; k < monsters.size(); k++) {
						monsters.get(k).move(ns);
					}
				}
				// item affect END_MOVE
				ns.itemsEffect(Status.END_MOVE);
				if (!ns.isLose()) {
					moves.add(ns);
				}
				initItems.add(j, item);
			}

		}
	}

	public List<Status> buildAMove(SolutionPath path, List<Status> savedStatus)
			throws Exception {
		ArrayList<Status> moves = new ArrayList<Status>();
		Status s = path.getStatus();
		manMove(s, moves);
		itemUse(s, moves);
		for (int i = 0; i < moves.size(); i++) {
			if (savedStatus.contains(moves.get(i))) {
				moves.remove(i);
				i--;
			} else {
				savedStatus.add(moves.get(i));
			}
		}
		return moves;
	}

	public void browser(SolutionPath path, Solution solution,
			List<Status> savedStatus) throws Exception {

		// build a list move
		List<Status> moves = buildAMove(path, savedStatus);
		if (moves != null && moves.size() > 0) {
			SolutionPath child = new SolutionPath();
			Status s = null;
			for (int i = 0; i < moves.size(); i++) {
				child.setPre(path);
				s = moves.get(i);
				child.setStatus(s);
				path.setNext(child);
				// check end
				if (s.isWin()) {
					// ghi nhan ket qua
					System.out.println("Win");
					solution.getPath().add((SolutionPath) child.clone());

				} else {
					browser(child, solution, savedStatus);
				}
			}
		}
	}

	public Solution getSolution() throws Exception {
		Solution solution = new Solution();
		SolutionPath root = new SolutionPath();
		root.setStatus(status);
		List<Status> savedStatus = new ArrayList<Status>();
		savedStatus.add(status);
		browser(root, solution, savedStatus);
		return solution;
	}

	public void printSolution(SolutionPath path, SolutionPath path2) {
		if (path != null && path2 != null) {
			Location l1 = path.getStatus().getManLocation();
			Location l2 = path2.getStatus().getManLocation();
			System.out.println("Man move: (" + (l1.getX() - l2.getX()) + ":"
					+ (l1.getY() - l2.getY()) + ")");
			List<Monster> monsters = path.getStatus().getMonsters();
			List<Monster> monsters2 = path.getStatus().getMonsters();
			if (monsters != null && monsters2 != null) {
				for (int i = 0; i < monsters.size() && i < monsters2.size(); i++) {
					l1 = monsters.get(i).getLocation();
					l2 = monsters2.get(i).getLocation();
					System.out.println("monsters " + i + " location:(" +l1.getX()+":"+l1.getY()+") L:"
					+ path.getStatus().canMoveLeft(l1.getX(), l1.getY())+",R:"
					+ path.getStatus().canMoveRight(l1.getX(), l1.getY())+ ",U:"
					+ path.getStatus().canMoveUp(l1.getX(), l1.getY())+ ",D:"
					+ path.getStatus().canMoveDown(l1.getX(), l1.getY()));
					System.out.println("monsters " + i + " move: ("
							+ (l1.getX() - l2.getX()) + ":"
							+ (l1.getY() - l2.getY()) + ")");
				}
			}
		}
	}

	public void printSolution(Solution solution) {
		System.out
				.println("--------------------------------------------------");
		if (solution.getPath() != null) {
			SolutionPath path, tmp;

			for (int i = 0; i < solution.getPath().size(); i++) {
				System.out
						.println("++++++++++++++++++++++++++++++++++++++++++++++++++");
				path = solution.getPath().get(i);
				if (path != null) {
					tmp = path.getPre();
					while (tmp != null) {
						printSolution(tmp, tmp.getPre());
						tmp = tmp.getPre();
					}
				}
			}
		}
		System.out
				.println("--------------------------------------------------");
	}

	public Status getStatus() {
		return status;
	}

	public static void main(String[] args) throws IOException, Exception {
		MCreator creator = new MCreator();
		creator.init();
		FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
		ObjectOutputStream objectOutputStream = new ObjectOutputStream(
				fileOutputStream);
		Solution solution = creator.getSolution();
		creator.printSolution(solution);
		objectOutputStream.writeObject(solution);
		fileOutputStream.flush();
		fileOutputStream.close();

	}

}
